
/*
 * Copyright (c) 2008 - 2009 NVIDIA Corporation.  All rights reserved.
 *
 * NVIDIA Corporation and its licensors retain all intellectual property and proprietary
 * rights in and to this software, related documentation and any modifications thereto.
 * Any use, reproduction, disclosure or distribution of this software and related
 * documentation without an express license agreement from NVIDIA Corporation is strictly
 * prohibited.
 *
 * TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED *AS IS*
 * AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS OR IMPLIED,
 * INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE.  IN NO EVENT SHALL NVIDIA OR ITS SUPPLIERS BE LIABLE FOR ANY
 * SPECIAL, INCIDENTAL, INDIRECT, OR CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT
 * LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF
 * BUSINESS INFORMATION, OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR
 * INABILITY TO USE THIS SOFTWARE, EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGES
 */

#include <optix.h>
#include <optixu/optixu_math_namespace.h> 
#include "device_util.h"

using namespace optix;

rtDeclareVariable(rtObject,     top_object, , );
rtDeclareVariable(float,        scene_epsilon, , );
rtDeclareVariable(int,          max_depth, , );
rtDeclareVariable(unsigned int, radiance_ray_type, , );
rtDeclareVariable(unsigned int, shadow_ray_type, , );
rtDeclareVariable(float3, shading_normal, attribute shading_normal, ); 
rtDeclareVariable(float3, geometric_normal, attribute geometric_normal, ); 
//rtDeclareVariable(float3, front_hit_point, attribute front_hit_point, );
//rtDeclareVariable(float3, back_hit_point, attribute back_hit_point, );

rtDeclareVariable(optix::Ray, ray, rtCurrentRay, );
rtDeclareVariable(float, t_hit, rtIntersectionDistance, );

rtDeclareVariable(float3,       shadow_transparency, , );
rtDeclareVariable(float,        importance_cutoff, , );
rtDeclareVariable(float3,       cutoff_color, , );
rtDeclareVariable(float,        fresnel_exponent, , );
rtDeclareVariable(float,        fresnel_minimum, , );
rtDeclareVariable(float,        fresnel_maximum, , );
rtDeclareVariable(float,        refraction_index, , );
rtDeclareVariable(int,          refraction_maxdepth, , );
rtDeclareVariable(int,          reflection_maxdepth, , );
rtDeclareVariable(float3,       refraction_color, , );
rtDeclareVariable(float3,       reflection_color, , );
rtDeclareVariable(float3,       extinction_constant, , );

rtDeclareVariable(float3, front_hit_point, attribute front_hit_point, );
rtDeclareVariable(float3, back_hit_point, attribute back_hit_point, );


rtDeclareVariable(uint2, launch_index, rtLaunchIndex, );

rtDeclareVariable(ViewRayData, ray_payload, rtPayload, );
// -----------------------------------------------------------------------------

__device__ __inline__ float3 TraceRay(float3 origin, float3 direction, int depth, float importance )
{
  optix::Ray ray = optix::make_Ray( origin, direction, 0, 0.f, RT_DEFAULT_MAX );
  ViewRayData prd; 
  prd.depth   = depth; 
	prd.normal = make_float3(0.f);
	prd.position = make_float3(0.f);
	prd.color = make_float3(1.f);
	prd.importance = importance;

  rtTrace( top_object, ray, prd );
  return prd.color;
}

__device__ __inline__ float3 exp( const float3& x )
{
  return make_float3(exp(x.x), exp(x.y), exp(x.z));
}

// -----------------------------------------------------------------------------

RT_PROGRAM void closest_hit()
{ 
  
	float3 n = normalize(rtTransformNormal(RT_OBJECT_TO_WORLD, shading_normal)); // normal
  const float3 fhp = rtTransformPoint(RT_OBJECT_TO_WORLD, front_hit_point);
  const float3 bhp = rtTransformPoint(RT_OBJECT_TO_WORLD, back_hit_point);

#if 1
  const float3 i = ray.direction;                                            // incident direction
        float3 t;                                                            // transmission direction
        float3 r;                                                            // reflection direction

  float reflection = 1.0f;
  float3 result = make_float3(0.0f);
  
  const int depth = ray_payload.depth;

  float3 beer_attenuation;
  if(dot(n, ray.direction) > 0) {
    // Beer's law attenuation
    beer_attenuation = make_float3(1);//exp(extinction_constant * t_hit);
  } else {
    beer_attenuation = make_float3(1);
  }

	

  // refraction
  if (depth < min(refraction_maxdepth, max_depth))
  {
    if ( refract(t, i, n, refraction_index) )
    {
      // check for external or internal reflection
      float cos_theta = dot(i, n);
      if (cos_theta < 0.0f)
        cos_theta = -cos_theta;
      else
        cos_theta = dot(t, n);

      reflection = fresnel_schlick(cos_theta, fresnel_exponent, fresnel_minimum, fresnel_maximum);

			//TODO: remove importance and work with max depth only
      float importance = ray_payload.importance * (1.0f-reflection) * optix::luminance( refraction_color * beer_attenuation );
      float3 color = cutoff_color;
      if ( importance > importance_cutoff ) {
        color = TraceRay(bhp, t, depth+1, importance);
      }
      result += (1.0f - reflection) * refraction_color * color;
    }
    // else TIR
  } // else reflection==1 so refraction has 0 weight

	

  // reflection			
  float3 color = cutoff_color;
  if ( depth < min(reflection_maxdepth, max_depth) )
  {
    r = reflect(i, n);
  
    float importance = ray_payload.importance * reflection * optix::luminance( reflection_color * beer_attenuation );
    if ( importance > importance_cutoff ) {
      color = TraceRay( fhp, r, depth+1, importance );
    }
  }
  result += reflection * reflection_color * color;

  result = result * beer_attenuation; 
	ray_payload.color = result;
#endif
	
	//float3 n = shading_normal; 
	/* n.x = fabsf(n.x);
	n.y = fabsf(n.y);
	n.z = fabsf(n.z); 
  ray_payload.color = n;
	*/
  
}



// -----------------------------------------------------------------------------

//
// Attenuates shadow rays for shadowing transparent objects
//  
rtDeclareVariable(ShadowRayData, prd, rtPayload, );
RT_PROGRAM void glassShadowHit()
{
  float3 world_normal = normalize( rtTransformNormal( RT_OBJECT_TO_WORLD, shading_normal ) );
  float nDi = fabs(dot(world_normal, ray.direction));

	float3 interm = 1-fresnel_schlick(nDi, 5, shadow_transparency, make_float3(1.f));
  prd.atten *= interm.x;
  if(optix::luminance(make_float3(prd.atten)) < importance_cutoff)
    rtTerminateRay();
  else
    rtIgnoreIntersection();
}


RT_PROGRAM void radiance_miss()
{
	ray_payload.color = make_float3(0.f, 0.f, 0.f);
}